import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgClientSync, InputTypeEnum, IState, toFixed } from "../Common";
import { Connection } from "../Core";
import type Player from "./Player";
import PlayerManager from "./PlayerManager";
import RoomManager from "./RoomManager";

export default class Room {
  id: number;
  players: Set<Player> = new Set();

  private lastTime?: number;
  private timers: NodeJS.Timer[] = [];
  private pendingInput: Array<IClientInput> = [];
  private lastPlayerFrameIdMap: Map<number, number> = new Map();

  constructor(rid: number) {
    this.id = rid;
  }

  join(uid: number) {
    const player = PlayerManager.Instance.getPlayerById(uid);
    if (player) {
      player.rid = this.id;
      this.players.add(player);
    }
  }

  leave(uid: number) {
    const player = PlayerManager.Instance.getPlayerById(uid);
    if (player) {
      player.rid = -1;
      player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
      this.players.delete(player);
      if (!this.players.size) {
        RoomManager.Instance.closeRoom(this.id);
      }
    }
  }

  close() {
    this.timers.forEach((t) => clearInterval(t));
    for (const player of this.players) {
      player.rid = -1;
      player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {});
      player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
    }
    this.players.clear();
  }

  sync() {
    for (const player of this.players) {
      player.connection.sendMsg(ApiMsgEnum.MsgRoom, {
        room: RoomManager.Instance.getRoomView(this),
      });
    }
  }

  start() {
    const state: IState = {
      actors: [...this.players].map((player, index) => ({
        id: player.id,
        nickname: player.nickname,
        position: {
          x: -150 + index * 300,
          y: -150 + index * 300,
        },
        direction: {
          x: 1,
          y: 0,
        },
        hp: 100,
        type: index === 0 ? EntityTypeEnum.Actor1 : EntityTypeEnum.Actor2,
        weaponType: index === 0 ? EntityTypeEnum.Weapon1 : EntityTypeEnum.Weapon2,
        bulletType: index === 0 ? EntityTypeEnum.Bullet1 : EntityTypeEnum.Bullet2,
      })),
      bullets: [],
      nextBulletId: 1,
    };

    for (const player of this.players) {
      player.connection.sendMsg(ApiMsgEnum.MsgGameStart, {
        state,
      });
      player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
    }
    let t1 = setInterval(() => {
      this.sendServerMsg();
    }, 100);
    let t2 = setInterval(() => {
      this.timePast();
    }, 16);
    this.timers = [t1, t2];
  }

  getClientMsg(connection: Connection, { frameId, input }: IMsgClientSync) {
    this.lastPlayerFrameIdMap.set(connection.playerId, frameId);
    this.pendingInput.push(input);
  }

  sendServerMsg() {
    const pendingInput = this.pendingInput;
    this.pendingInput = [];

    for (const player of this.players) {
      player.connection.sendMsg(ApiMsgEnum.MsgServerSync, {
        lastFrameId: this.lastPlayerFrameIdMap.get(player.id) ?? 0,
        inputs: pendingInput,
      });
    }
  }

  timePast() {
    let now = process.uptime();
    const dt = now - (this.lastTime ?? now);
    this.pendingInput.push({
      type: InputTypeEnum.TimePast,
      dt: toFixed(dt),
    });
    this.lastTime = now;
  }
}
